#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys

from conf.jinja2_tool import jinja2_dump
from k8s.env import TEMPLATES_DIR, CONF_DIR
from conf.yaml_tool import yaml_load
from log.logger import logger
from collections import OrderedDict
from misc.singleton import SingletonWithParams
from pg import reserved
from pg import region as pg_region
import copy
from k8s.env import KEYS
from k8s.secrets import _gen_pgpass
from misc.color import INFO, SINFO, CRIT, SUMM, WARN
from misc.net import get_valid_address, get_valid_network


@SingletonWithParams
class Config(object):
    def __init__(self, arch, package=None):
        self.arch = arch
        self.configs = {}
        self.region_configs = {}
        self.vip_configs = {}
        self.key_configs = {}
        self.password_configs = {}
        self.mode_map = {}
        self.hosts = []
        self.vm_ips = {}
        self.__load_configs(arch, package)
        if not self.configs:
            msg = "Load config from [{0}] failed!".format(TEMPLATES_DIR)
            logger.error(msg)
            print(CRIT % ("Error:" + msg))

    def __load_configs(self, arch, package_name):
        for directory in os.listdir(TEMPLATES_DIR):
            dir_path = os.path.join(TEMPLATES_DIR, directory)
            arch_path = os.path.join(dir_path, arch)
            config_path = os.path.join(arch_path, "config.yaml")
            if os.path.isfile(config_path):
                configs = yaml_load(config_path)
                package = configs["package"]
                if package_name and package != package_name:
                    continue
                logger.debug("load config {0}".format(config_path))
                # merge image info
                if "images" in configs and configs["images"]:
                    self.configs.setdefault("images", {})[package] = configs["images"]
                # merge role info
                for role, role_info in configs.get("roles", {}).iteritems():
                    for component in role_info:
                        try:
                            role_info[component]["package"] = package
                        except Exception:
                            print(role_info, component, package)
                            exit()
                    self.configs.setdefault("roles", {}).setdefault(role, {}).update(role_info)
                # merge operators
                for operator, operator_info in configs.get("operators", {}).iteritems():
                    operator_info["package"] = package
                    self.configs.setdefault("operators", {})[operator] = operator_info

    def get_roles(self):
        return self.configs['roles'].keys()

    def get_components_list(self):
        role_components_list = self.get_roles_components()
        components_list = []
        for role in role_components_list:
            components_list.extend(role_components_list[role])
        return components_list

    def get_roles_components(self):
        res = OrderedDict()
        for role in self.get_roles():
            components = self.configs['roles'][role].keys()
            res[role] = components
        return res

    def gen_configurations(self, zone_id):
        from pg import switch
        from pg import zone as pg_zone
        region_id = pg_region.get_region_id()
        configuration = {}
        configuration['configs'] = {}
        configuration['configs']['images'] = self.configs['images']
        configuration['configs']['regionvar'] = pg_region.get_region_vars()
        configuration['configs']['svc_ips'] = get_vip_configs()
        configuration['configs']['access_keys'] = get_keys_configs()
        configuration['configs']['password'] = get_password_configs()
        configuration['configs']['mode_map'] = get_mode_map()
        configuration['configs']['hosts'] = get_hosts()
        configuration['configs']['vm_ips'] = get_vm_ips()
        configuration['configs']['switch'] = switch.get_zone_switches(zone_id=zone_id)
        configuration['configs']['multus'] = get_multus_configs()
        if zone_id != region_id:
            configuration['configs']['zonevar'] = pg_zone.get_zone_vars(zone_id)
        else:
            for zone in pg_region.get_zone_ids():
                configuration['configs'].setdefault('zonevars', {})[zone] = pg_zone.get_zone_vars(zone)
        return configuration

    def gen_components_yamls(self, zone_id):
        from pg import roles as pg_roles
        from pg import cm_components
        from pg import zone as pg_zone
        from pg import switch
        region_id = pg_region.get_region_id()
        configuration = self.gen_configurations(zone_id=zone_id)
        external_access_components = cm_components.get_zone_components_by_external_access(zone_id=zone_id, external_access=1)

        if zone_id == region_id:
            # 如果参数zone_id 为region id， 则生成per_region的组件的yaml
            cm_roles = []
            for zone in pg_region.get_zone_ids():
                cm_roles.extend(pg_roles.get_cm_roles(zone))
            # 获取region内以容器方式部署的role集合
            cm_roles = list(set(cm_roles))
            per_region_components = cm_components.get_per_region_components()
            # 如果某个per region组件对应的role在上述集合中，则生成该组件的yaml
            for cm in per_region_components:
                role = cm['role']
                if role in cm_roles:
                    self.gen_component_yaml(zone_id,
                                            cm['package'],
                                            self.arch,
                                            cm['component'],
                                            configuration)

        # 如果传入的参数不是region id，就按role生成各组件的yaml
        else:
            cm_roles = pg_roles.get_cm_roles(zone_id)
            for role in cm_roles:
                self.gen_role_yamls(zone_id, role, configuration)

        # 有的组件对应的role可能不是cm部署，但仍通过svc暴露vip， endpoint使用vm, 如pgpool
        for component in external_access_components:
            self.gen_component_yaml(zone_id, component['package'], self.arch, component['component'], configuration)

    def gen_operator_yamls(self):
        region_id = pg_region.get_region_id()
        configuration = {}
        configuration['configs'] = self.configs
        configuration['configs']['svc_ips'] = get_vip_configs()
        handled_packages = []
        for operator in configuration['configs'].get("operators", {}):
            package = self.configs["operators"][operator]['package']
            operator_templates_dir = os.path.join(TEMPLATES_DIR, package, self.arch, 'operators')
            if package in handled_packages:
                continue
            if not os.path.exists(operator_templates_dir):
                continue
            for template_file in os.listdir(operator_templates_dir):
                target_yaml_file = os.path.join(CONF_DIR, region_id, 'operator', template_file.strip(".j2"))
                jinja2_dump(operator_templates_dir, template_file, configuration, target_yaml_file)
            handled_packages.append(package)

    # 在component的模板目录下遍历所有的yaml， 用configuration中的变量渲染模板得到yaml
    def gen_component_yaml(self, zone_id, package, arch, component, configuration):
        from pg import cm_components

        msg = "Building yaml for component [%s], namespace: [%s] ..." % (component, zone_id)
        logger.info(msg)
        print(INFO % ("|--Info: " + msg))

        # 从db获取组件的各项配置
        component_data = cm_components.get_component(component=component, zone_id=zone_id)
        if not component_data:
            # 如果按zone id没获取到该组件的信息，该组件又不是per region的组件， 则告警
            if component not in [cm['component'] for cm in cm_components.get_per_region_components()]:
                msg = "Warning: No data found for component [%s], namespace [%s]" % (component, zone_id)
                logger.warn(msg)
                print(WARN % ("  |--Warning: " + msg))
            return
        component_templates_dir = os.path.join(TEMPLATES_DIR, package, arch, component)
        # 检查有没有组件的模板目录，没有的话就报错
        if not os.path.exists(component_templates_dir):
            msg = "Template directory for [%s] does not exist: [%s]!" % (component, component_templates_dir)
            logger.error(msg)
            print(CRIT % ("  |--Error: "+msg))
            return
        configuration = copy.deepcopy(configuration)
        if 'containers' not in configuration['configs']:
            role = component_data['role']
            role_config = self.configs['roles'].get(role, None)
            component_config = role_config.get(component, None)
            zone_svc_ips = configuration['configs']['svc_ips'].get(zone_id, None)
            if zone_svc_ips:
                component_config['cluster_ip'] = zone_svc_ips.get(component, 'None')
            configuration['configs'].update(component_config)

        for template_file in os.listdir(component_templates_dir):
            target_yaml_file = os.path.join(CONF_DIR, zone_id, component, template_file.strip(".j2"))
            # 如果目标文件不是deployment/sts/ds(.yaml), 且目标文件已存在，就不再生成该文件
            # 目的是为了让pv/cm(.yaml)这些文件生成后，更新时不会被覆盖
            if target_yaml_file.split('/')[-1] not in ['deployment.yaml', 'sts.yaml', 'ds.yaml'] and os.path.exists(target_yaml_file):
                continue
            jinja2_dump(component_templates_dir, template_file, configuration, target_yaml_file)

    # 生成指定role相关的components的所有yaml
    def gen_role_yamls(self, zone_id, role, configuration):
        from pg import cm_components
        # 检查配置文件中有没有指定role的配置， 如果没有的话就报错
        role_config = self.configs['roles'].get(role, None)

        if not role_config:
            msg = "No config found for role [%s] in configs.yaml" % role
            logger.error(msg)
            print(CRIT % ("|--Error: "+msg))
            return

        arch = self.arch

        role_components = cm_components.get_components_by_role(role, zone_id)
        for component in role_components:
            configuration = copy.deepcopy(configuration)
            # 检查配置文件中有没有组件相关的配置，没有的话就报错, 继续检查下一个组件
            component_config = role_config.get(component, None)
            if not component_config:
                msg = "No config found for role [%s] component [%s] in configs.yaml" % (role, component)
                logger.error(msg)
                print(CRIT % ("|--Error: "+msg))
                continue
            package = component_config['package']
            component_config['cluster_ip'] = configuration['configs']['svc_ips'][zone_id].get(component, 'None')
            configuration['configs'].update(component_config)

            self.gen_component_yaml(zone_id, package, arch, component, configuration)


def get_vip_configs():
    from pg import cm_components
    from pg import roles as pg_roles
    region_id = pg_region.get_region_id()
    svc_ip_configs = {}
    zone_ids = pg_region.get_zone_ids()
    zone_ids.append(region_id)
    for zone_id in zone_ids:
        if zone_id not in svc_ip_configs:
            svc_ip_configs[zone_id] = {}

        components = cm_components.get_zone_components_by_headless(zone_id=zone_id, headless=0)
        for component in components:
            # 如果是region_id的，就不检查role的deploy字段（因为没有）
            if zone_id != region_id:
                role = component['role']
                if not pg_roles.get_role(zone_id=zone_id, role=role)['deploy']:
                    continue
            vip = reserved.get_vip(zone_id=zone_id, component=component['component'])
            svc_ip_configs[zone_id].update({component['component']: vip})

    svc_ip_configs['system'] = {}
    for i in reserved.get_reserved_ips_by_zone_id(zone_id='None'):
        key = i['name']
        value = i['value']
        svc_ip_configs['system'][key] = value

    return svc_ip_configs


def get_vm_ips():
    vm_ip_configs = {}
    vm_ips = reserved.get_by_type(type='vm_ip')
    for item in vm_ips:
        zone_id = item['zone_id']
        name = item['name']
        if zone_id not in vm_ip_configs:
            vm_ip_configs[zone_id] = {}
        vm_ip_configs[zone_id][name] = item['value']
    return vm_ip_configs


def get_multus_configs():
    from pg import zone as pg_zone
    from pg import network
    multus_configs = {}
    multus_ips = []
    multus_ips.extend(reserved.get_by_type(type='multus_mgmt_ip'))
    multus_ips.extend(reserved.get_by_type(type='multus_user_ip'))
    # multus_ips.extend(reserved.get_by_type(type='multus_storage_ip'))

    # 获取region内所有的mgmt_networks/user_networks
    mgmt_networks = []
    for zone in pg_region.get_zone_ids():
        mgmt_networks.extend(pg_zone.get_zone_vars(zone)['mgmt_networks'].split(','))

    user_networks = []
    for zone in pg_region.get_zone_ids():
        networks = pg_zone.get_zone_vars(zone)['user_networks']
        if not networks:
            continue
        else:
            user_networks.extend(networks.split(','))

    if not user_networks:
        user_networks = mgmt_networks

    for item in multus_ips:
        zone_id = item['zone_id']
        name = item['name']
        multus_configs.setdefault(zone_id, {}).setdefault(name, {"mgmt": {}, "user": {}})
        if item['type'] == 'multus_mgmt_ip':
            address = item['value']
            for mgmt_network in mgmt_networks:
                if get_valid_address(address) in get_valid_network(mgmt_network):
                    cidr = mgmt_network
                    break
            gateway = network.get_networks_by_cidr(cidr)[0]["gateway"]
            nic = {
                "address": address,
                "gateway": gateway,
                "cidr": cidr,
            }
            multus_configs[zone_id][name]['mgmt'] = nic

        elif item['type'] == 'multus_user_ip':
            address = item['value']
            for user_network in user_networks:
                if get_valid_address(address) in get_valid_network(user_network):
                    cidr = user_network
                    break
            gateway = network.get_networks_by_cidr(cidr)[0]["gateway"]
            nic = {
                "address": address,
                "gateway": gateway,
                "cidr": cidr,
            }
            multus_configs[zone_id][name]['user'] = nic

    return multus_configs


def get_keys_configs():
    keys_configs = {}
    for item in KEYS:
        with open(KEYS[item]['key_id_file']) as f:
            key_id = f.read().strip()
        with open(KEYS[item]['access_secret_key_file']) as f:
            access_secret_key = f.read().strip()
        with open(KEYS[item]['secret_access_key_file'], 'rb') as f:
            secret_access_key = f.read().strip()
        keys_configs[item] = {
            "key_id": key_id,
            "access_secret_key": access_secret_key,
            "secret_access_key": secret_access_key,
        }
    return keys_configs


def get_password_configs():
    password_configs = {}
    password_configs['pg'] = _gen_pgpass()
    return password_configs


def get_mode_map():
    mode_map = {}
    from pg import roles as pg_roles
    zone_ids = pg_region.get_zone_ids()
    for zone_id in zone_ids:
        mode_map[zone_id] = {}
        cm_roles = pg_roles.get_cm_roles(zone_id=zone_id)
        vm_roles = pg_roles.get_vm_roles(zone_id=zone_id)
        for cm_role in cm_roles:
            mode_map[zone_id][cm_role] = 'cm'
        for vm_role in vm_roles:
            mode_map[zone_id][vm_role] = 'vm'
    return mode_map


def get_hosts():
    ip_hosts = {}
    with open('/etc/hosts', 'r') as f:
        for line in f.readlines():
            if '127.0.1.1' in line or line.startswith('#') or not line.strip():
                continue
            try:
                ip = line.strip().split()[0]
                hosts = line.strip().split()[1:]
                if ip_hosts.has_key(ip):
                    hosts.extend(ip_hosts[ip])
                    hosts = list(set(hosts))
                ip_hosts[ip] = sorted(hosts)
            except Exception:
                continue
    return ip_hosts
